home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
722
/
722.xpi
/
chrome
/
noscript.jar
/
content
/
noscript
/
ABE.js
< prev
next >
Wrap
Text File
|
2010-02-12
|
37KB
|
1,272 lines
INCLUDE('IOUtil', 'antlr', 'ABEParser', 'ABELexer', 'AddressMatcher', 'Thread', 'Lang');
const ABE = {
FLAG_CALLED: 0x01,
FLAG_CHECKED: 0x02,
SITE_RULESET_LIFETIME: 12 * 60 * 60000, // 12 hours
maxSiteRulesetSize: 8192,
maxSiteRulesetTime: 16000, // 4kbit/sec :P
enabled: false,
siteEnabled: false,
legacySupport: false,
allowRulesetRedir: false,
skipBrowserRequests: true,
BROWSER_URI: IOS.newURI("chrome://browser/content/", null, null),
LOAD_BACKGROUND: CI.nsIChannel.LOAD_BACKGROUND,
LOAD_INITIAL_DOCUMENT_URI: CI.nsIChannel.LOAD_INITIAL_DOCUMENT_URI,
SANDBOX_KEY: "abe.sandbox",
localRulesets: [],
_localMap: null,
_siteRulesets: null,
siteMap: {},
get disabledRulesetNames() {
return this.rulesets.filter(function(rs) { return rs.disabled; })
.map(function(rs) { return rs.name; }).join(" ");
},
set disabledRulesetNames(names) {
var rs;
this.updateRules();
for each (rs in this.rulesets) rs.disabled = false;
if (names) try {
for each (var name in names.split(/\s+/)) {
rs = this.localMap[name] || this.siteMap[name];
if (rs) rs.disabled = true;
}
} catch(e) {}
return names;
},
get localMap() {
if (this._localMap) return this._localMap;
this._localMap = {};
for each (var rs in this.localRulesets) {
this._localMap[rs.name] = rs;
}
return this._localMap;
},
get siteRulesets() {
if (this._siteRulesets) return this._siteRulesets;
this._siteRulesets = [];
var rs;
for (var name in this.siteMap) {
rs = this.siteMap[name];
if (rs && !rs.empty) this._siteRulesets.push(rs);
}
this._siteRulesets.sort(function(r1, r2) { return r1.name > r2.name; });
return this._siteRulesets;
},
get rulesets() {
return this.localRulesets.concat(this.siteRulesets);
},
checkFrameOpt: function(w, chan) {
try {
if (!w) {
var ph = PolicyState.extract(chan);
var ctx = ph.context;
w = ctx.self || ctx.ownerDocument.defaultView;
}
switch (chan.getResponseHeader("X-FRAME-OPTIONS").toUpperCase()) {
case "DENY":
return true;
case "SAMEORIGIN":
return chan.URI.prePath != w.top.location.href.match(/^https?:\/\/[^\/]*/i)[0];
}
} catch(e) {}
return false;
},
clear: function() {
this.localRulesets = [];
this.siteMap = {};
this._siteRulesets = null;
ABEStorage.reset();
},
refresh: function() {
var disabled = this.disabledRulesetNames;
this.clear();
this.updateRulesNow();
this.disabledRulesetNames = disabled;
},
parse: function(name, source, timestamp) {
try {
var rs = new ABERuleset(name, source, timestamp);
if (rs.site) {
this.putSiteRuleset(rs);
} else {
this.addLocalRuleset(rs);
}
return rs;
} catch(e) {
this.log(e);
}
return false;
},
addLocalRuleset: function(rs) {
this.localRulesets.push(rs);
this._localMap = null;
},
putSiteRuleset: function(rs) {
this.siteMap[rs.name] = rs;
this._siteRulesets = null;
},
serialize: function() {
var data = [];
for each (var rs in this.localRulesets) {
data.push({
source: rs.source,
name: rs.name,
timestamp: rs.timestamp,
disabled: rs.disabled
});
}
return data;
},
restore: function(data) {
if (!data.length) return;
var f, change;
try {
ABEStorage.clear();
ABEStorage.reset();
for each(var rs in data) {
f = ABEStorage.getRulesetFile(rs.name);
if (!f.exists()) f.create(f.NORMAL_FILE_TYPE, 0600);
IO.safeWriteFile(f, rs.source);
f.lastModifiedTime = rs.timestamp;
}
ABEStorage.loadRulesNow();
} catch(e) {
ABE.log("Failed to restore configuration: " + e);
}
},
resetDefaults: function() {
ABEStorage.clear();
this.clear();
this.updateRulesNow();
},
updateRules: function() {
return ABEStorage.loadRules();
},
updateRulesNow: function(reset) {
if (reset) ABEStorage.reset();
return ABEStorage.loadRulesNow();
},
getRulesetFile: function(name) {
return ABEStorage.getRulesetFile(name);
},
checkPolicy: function(origin, destination, method) {
try {
var res = this.checkRequest(new ABERequest(new ABEPolicyChannel(origin, destination, method)));
return res && res.fatal;
} catch(e) {
ABE.log(e);
return false;
}
},
checkRequest: function(req) {
if (!(this.enabled && (Thread.canSpin || this.legacySupport)))
return false;
const channel = req.channel;
const loadFlags = channel.loadFlags;
var browserReq = req.originURI.schemeIs("chrome") && !req.external;
if (browserReq &&
(
this.skipBrowserRequests &&
((loadFlags & this.LOAD_BACKGROUND) ||
!req.isDoc && req.origin == ABE.BROWSER_URI.spec && !req.window)
)
) {
if (this.consoleDump) this.log("Skipping low-level browser request for " + req.destination);
return false;
}
this.updateRules();
if (this.localRulesets.length == 0 && !this.siteEnabled)
return null;
if (this.deferIfNeeded(req))
return false;
var t;
if (this.consoleDump) {
this.log("Checking #" + req.serial + ": " + req.destination + " from " + req.origin + " - " + loadFlags);
t = Date.now();
}
try {
var res = new ABERes(req);
var rs;
for each (rs in this.localRulesets) {
if (this._check(rs, res)) break;
}
if (!(browserReq || res.fatal) &&
this.siteEnabled && channel instanceof CI.nsIHttpChannel &&
!IOUtil.extractFromChannel(channel, "ABE.preflight", true) &&
req.destinationURI.schemeIs("https") &&
req.destinationURI.prePath != req.originURI.prePath &&
!(this.skipBrowserRequests && req.originURI.schemeIs("chrome") && !req.window) // skip preflight for window-less browser requests
) {
var name = this._host2name(req.destinationURI.host);
if (!(name in this.siteMap)) {
ABE.log("Preflight for " + req.origin + ", " + req.destination + ", " + loadFlags);
this.downloadRuleset(name, req.destinationURI, req.sameQueue);
}
rs = this.siteMap[name];
if (rs && Date.now() - rs.timestamp > this.SITE_RULESET_LIFETIME)
rs = this.downloadRuleset(name, req.destinationURI, req.sameQueue);
if (rs) this._check(rs, res);
}
} finally {
if (this.consoleDump) this.log(req.destination + " Checked in " + (Date.now() - t));
req.checkFlags |= this.FLAG_CHECKED;
}
return res.lastRuleset && res;
},
_check: function(rs, res) {
var action = rs.check(res.request);
if (action) {
var r = rs.lastMatch;
this.log(r);
this.log(res.request + ' matches "' + r.lastMatch + '"');
(res.rulesets || (res.rulesets = [])).push(rs);
res.lastRuleset = rs;
return res.fatal = (res.request.channel instanceof ABEPolicyChannel)
? /^Deny$/i.test(action)
: ABEActions[action.toLowerCase()](res.request);
}
return false;
},
deferIfNeeded: function(req) {
var host = req.destinationURI.host;
if (!(req.canDoDNS && req.deferredDNS) ||
!ChannelReplacement.supported ||
DNS.isIP(host) ||
DNS.getCached(host) || // getCached() rather than isCached(), otherwise we defer even for lazy expiration
req.channel.redirectionLimit == 0 || req.channel.status != 0)
return false;
IOUtil.attachToChannel(req.channel, "ABE.deferred", DUMMYOBJ);
if (IOUtil.runWhenPending(req.channel, function() {
try {
if (req.channel.status != 0) return;
if ((req.channel instanceof CI.nsITransportEventSink) && req.isDoc && !req.subdoc ) try {
ABE.log("DNS notification for " + req.destination);
req.channel.onTransportStatus(null, 0x804b0003, 0, 0); // notify STATUS_RESOLVING
} catch(e) {}
var replacement = req.replace();
ABE.log(host + " not cached in DNS, deferring ABE checks after DNS resolution for request " + req.serial);
DNS.resolve(host, 0, function(dnsRecord) {
replacement.open();
});
} catch(e) {
ABE.log("Deferred ABE checks failed: " + e);
}
})) {
ABE.log(req.serial + " not pending yet, will check later.")
}
return true;
},
isDeferred: function(chan) {
return !!IOUtil.extractFromChannel(chan, "ABE.deferred", true);
},
hasSiteRulesFor: function(host) {
return this._host2Name(host) in this.siteMap;
},
_host2name: function(host) {
return "." + host;
},
isSubdomain: function(parentHost, childHost) {
if (parentHost.length > childHost.length) return false;
parentHost = "." + parentHost;
childHost = "." + childHost;
return parentHost == childHost.substring(childHost.length - parentHost.length);
},
_downloading: {},
downloadRuleset: function(name, uri, sameQueue) {
var host = uri.host;
var downloading = this._downloading;
if (Thread.canSpin && (host in downloading)) {
ABE.log("Already fetching rulesets for " + host);
// Thread.yieldAll();
// Thread.spinWithQueue({ get running() { return host in downloading; }});
// return false;
}
var ts = Date.now();
var ctrl = {
_r: true,
set running(v) { if (!v) delete downloading[host]; return this._r = v; },
get running() { return this._r; },
startTime: ts,
maxTime: ABE.maxSiteRulesetTime
};
var elapsed;
try {
downloading[host] = true;
this.log("Trying to fetch rules for " + host + ", sameQueue=" + sameQueue);
uri = uri.clone();
uri.scheme = "https";
uri.path = "/rules.abe";
var xhr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(CI.nsIXMLHttpRequest);
xhr.mozBackgroundRequest = true;
xhr.open("GET", uri.spec, Thread.canSpin); // async if we can spin our own event loop
var channel = xhr.channel; // need to cast
IOUtil.attachToChannel(channel, "ABE.preflight", DUMMYOBJ);
if (channel instanceof CI.nsIHttpChannel && !this.allowRulesetRedir)
channel.redirectionLimit = 0;
if (channel instanceof CI.nsICachingChannel)
channel.loadFlags |= channel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; // see bug 309424
xhr.onreadystatechange = function() {
switch(xhr.readyState) {
case 2:
if (xhr.status >= 400) {
ABE.log("Early abort with status " + xhr.status + " for ruleset at " + uri.spec);
break;
}
return;
case 3:
var size = xhr.responseText.length; // todo: use https://developer.mozilla.org/En/Using_XMLHttpRequest#Monitoring_progress
if (size > ABE.maxSiteRulesetSize) {
ABE.log("Ruleset at " + uri.spec + " too big: " + size + " > " + ABE.maxSiteRulesetSize);
break;
}
return;
case 4:
// end
ctrl.running = false;
return;
default: // 0, 1
return;
}
xhr.abort();
ctrl.running = false;
}
if (Thread.canSpin) {
var send = function() {
xhr.send(null);
return Thread.spin(ctrl);
};
if (sameQueue ? send() : Thread.runWithQueue(send)) {
var size = 0;
try {
size = xhr.responseText.length;
} catch(e) {}
ABE.log("Ruleset at " + uri.spec + " timeout: " + size + " chars received in " + ctrl.elapsed + "ms");
xhr.abort();
return false;
}
} else {
xhr.send(null);
}
if (xhr.channel != channel && xhr.channel) // shouldn't happen, see updateRedirectChain()...
this._handleDownloadRedirection(channel, xhr.channel);
if (xhr.status != 200)
throw new Error("Status: " + xhr.status);
if (!/^application\/|\babe\b|^text\/plain$/i.test(xhr.channel.contentType))
throw new Error("Content-type: " + xhr.channel.contentType);
var source = xhr.responseText || '';
elapsed = Date.now() - ts;
if (source) ABE.log("Fetched ruleset for "+ host + " in " + elapsed + "ms");
return this.parse(name, source);
} catch(e) {
elapsed = elapsed || Date.now() - ts;
this.log("Can't fetch " + uri.spec + " (" + elapsed + "ms elapsed)");
this.log(e.message);
} finally {
if (!(name in this.siteMap)) this.parse(name, '');
else this.siteMap[name].timestamp = ts;
ctrl.running = false;
}
return false;
},
isSandboxed: function(channel) {
return IOUtil.extractFromChannel(channel, ABE.SANDBOX_KEY, true);
},
setSandboxed: function(channel) {
IOUtil.attachToChannel(channel, ABE.SANDBOX_KEY, DUMMYOBJ);
},
sandbox: function(docShell) {
docShell.allowJavascript = docShell.allowPlugins =
docShell.allowMetaRedirects= docShell.allowSubframes = false;
},
updateRedirectChain: function(oldChannel, newChannel) {
this._handleDownloadRedirection(oldChannel, newChannel);
var redirectChain = this.getRedirectChain(oldChannel);
redirectChain.push(oldChannel.URI);
IOUtil.attachToChannel(newChannel, "ABE.redirectChain", redirectChain);
},
getRedirectChain: function(channel) {
var rc = IOUtil.extractFromChannel(channel, "ABE.redirectChain", true);
if (!rc) {
var origin = ABERequest.getOrigin(channel);
rc = origin ? [origin] : [];
};
return rc;
},
getOriginalOrigin: function(channel) {
var rc = this.getRedirectChain(channel);
return rc.length && rc[0] || null;
},
_handleDownloadRedirection: function(oldChannel, newChannel) {
if (!IOUtil.extractFromChannel(oldChannel, "ABE.preflight", true)) return;
var uri = oldChannel.URI;
var newURI = newChannel.URI;
if (uri.spec != newURI.spec && // redirected, check if it same path and same domain or upper
(uri.path != newURI.path ||
!(newURI.schemeIs("https") && this.isSubdomain(newURI.host, uri.host))
)
) {
var msg = "Illegal ABE rule redirection " + uri.spec + " -> " + newURI.spec;
ABE.log(msg);
oldChannel.cancel(NS_BINDING_ABORTED);
throw new Error(msg);
}
IOUtil.attachToChannel(oldChannel, "ABE.preflight", DUMMYOBJ);
},
consoleDump: false,
log: function(msg) {
if (this.consoleDump) {
if (msg.stack) msg = msg.message + "\n" + msg.stack;
dump("[ABE] " + msg + "\n");
}
}
}
function ABERes(req) {
this.request = req;
}
ABERes.prototype = {
rulesets: null,
lastRuleset: null,
fatal: false
}
var ABEActions = {
accept: function(req) {
return false;
},
deny: function(req) {
IOUtil.abort(req.channel, true);
return true;
},
anonymize: function(req, channel) {
channel = channel || req.channel;
if (channel.loadFlags & channel.LOAD_ANONYMOUS) // already anonymous
return false;
var uri = req.destinationURI;
var cookie;
try {
cookie = channel.getRequestHeader("Cookie");
} catch(e) {
cookie = '';
}
uri = IOUtil.anonymizeURI(uri.clone(), cookie);
if (channel.isPending()) { // channel is already opened, we must replace it
if (ChannelReplacement.supported) {
try {
var replacement = req.replace(
/^(?:GET|HEAD|OPTIONS)$/i.test(channel.requestMethod) ? null : "GET",
uri);
this.anonymize(req, replacement.channel);
replacement.open();
return false;
} catch(e) {
ABE.log(e);
}
}
ABE.log("Counldn't replace " + uri.spec + " for Anonymize, falling back to Deny.");
return this.deny(req);
}
if (uri.spec != channel.URI.spec) channel.URI.spec = uri.spec;
channel.setRequestHeader("Cookie", '', false);
channel.setRequestHeader("Authorization", '', false);
channel.loadFlags |= channel.LOAD_ANONYMOUS;
return false;
},
sandbox: function(req) {
ABE.setSandboxed(req.channel);
if (req.isDoc) {
var docShell = DOM.getDocShellForWindow(req.window);
if (docShell) ABE.sandbox(docShell);
}
return false;
}
}
function ABERuleset(name, source, timestamp) {
this.name = name;
this.site = /\./.test(name);
this.source = source;
this.empty = !source;
this.timestamp = timestamp || Date.now();
if (!this.empty) {
try {
// dirty hack
var self = this;
org.antlr.runtime.BaseRecognizer.prototype.emitErrorMessage = function(msg) {
// we abort immediately to prevent infinite loops
var m = msg.match(/^line (\d+)/i, msg);
if (m) throw new Error(msg, parseInt(m[1]), ABE.getRulesetFile(self.name)); // TODO: error console reporting w/ line num
throw new Error(msg)
};
this._init(new ABEParser(new org.antlr.runtime.CommonTokenStream(
new ABELexer(new org.antlr.runtime.ANTLRStringStream(source))))
.ruleset().getTree());
} catch(e) {
if (this.errors) this.errors.push(e.message)
else this.errors = [e.message];
}
}
}
ABERuleset.prototype = {
site: false,
empty: false,
errors: null,
disabled: false,
rules: [],
expires: 0,
_init: function(tree) {
var rule = null,
predicate = null,
accumulator = null,
history = [],
rules = [];
walk(tree);
if (!this.errors) this.rules = rules;
rule = predicate = accumulator = history = null;
function walk(tree) {
var node, t;
for (var j = 0, l = tree.getChildCount(); j < l; j++) {
node = tree.getChild(j);
examine(node);
walk(node.getTree());
}
}
function examine(node) {
var t = node.getToken();
switch(t.type) {
case ABEParser.T_SITE:
case ABEParser.EOF:
if (rule) commit();
if (t.type == ABEParser.T_SITE) {
rule = { destinations: [], predicates: [] };
accumulator = rule.destinations;
}
break;
case ABEParser.T_ACTION:
if (rule) {
rule.predicates.push(predicate = { actions: [], methods: [], origins: [] });
accumulator = predicate.actions;
}
break;
case ABEParser.T_METHODS:
accumulator = predicate.methods;
break;
case ABEParser.T_FROM:
accumulator = predicate.origins;
break;
case ABEParser.COMMENT:
break;
default:
if (accumulator) accumulator.push(node.getText());
}
}
function commit() {
rules.push(new ABERule(rule.destinations, rule.predicates));
rule = null;
}
},
lastMatch: null,
check: function(req) {
if (this.disabled) return '';
var res;
for each (var r in this.rules) {
res = r.check(req);
if (res) {
this.lastMatch = r;
return res;
}
}
return '';
}
}
function ABERule(destinations, predicates) {
this.destinations = destinations.join(" ");
this.destination = new AddressMatcher(destinations.filter(this._destinationFilter, this).join(" "));
this.predicates = predicates.map(ABEPredicate.create);
}
ABERule.prototype = {
local: false,
allDestinations: false,
lastMatch: null,
_destinationFilter: function(s) {
switch(s) {
case "SELF":
return false; // this is illegal, should we throw an exception?
case "LOCAL":
return !(this.local = true);
case "ALL":
return !(this.allDestinations = true);
}
return true;
},
check: function(req) {
if (!req.failed &&
(this.allDestinations ||
this.destination && this.destination.test(req.destination, req.canDoDNS, false) ||
this.local && req.localDestination)
) {
for each (var p in this.predicates) {
if (p.match(req)) {
this.lastMatch = p;
return p.action;
}
if (req.failed) break;
}
}
return '';
},
toString: function() {
var s = "Site " + this.destinations + "\n" + this.predicates.join("\n");
this.toString = function() { return s; };
return s;
}
}
function ABEPredicate(p) {
this.action = p.actions[0];
if (this.action == 'Accept') {
this.permissive = true;
} else if (/^(Logout|Anon)$/.test(this.action)) {
this.action = 'Anonymize';
}
this.methods = p.methods.join(" ");
if (this.methods.length) {
this.allMethods = false;
var mm = p.methods.filter(this._methodFilter, this);
if (mm.length) this.methodRx = new RegExp("^\\b(?:" + mm.join("|") + ")\\b$", "i");
}
this.origins = p.origins.join(" ");
if (p.origins.length) {
this.allOrigins = false;
if (this.permissive) { // if Accept any, accept browser URLs
p.origins.push("^(?:chrome|resource):");
}
this.origin = new AddressMatcher(p.origins.filter(this._originFilter, this).join(" "));
}
}
ABEPredicate.create = function(p) { return new ABEPredicate(p); };
ABEPredicate.prototype = {
permissive: false,
subdoc: false,
self: false,
local: false,
allMethods: true,
allOrigins: true,
methodRx: null,
origin: null,
_methodFilter: function(m) {
switch(m) {
case "SUB":
return !(this.subdoc = true);
case "ALL":
return !(this.allMethods = true);
}
return true;
},
_originFilter: function(s) {
switch(s) {
case "SELF":
return !(this.self = true);
case "LOCAL":
return !(this.local = true);
case "ALL":
return !(this.allOrigins = true);
}
return true;
},
match: function(req) {
return (this.allMethods || this.subdoc && req.isSubdoc ||
this.methodRx && this.methodRx.test(req.method)) &&
(this.allOrigins || this.self && req.isSelf ||
(this.permissive ? req.matchAllOrigins(this.origin) : req.matchSomeOrigins(this.origin)) ||
this.local && req.localOrigin
);
},
toString: function() {
var s = this.action;
if (this.methods) s += " " + this.methods;
if (this.origins) s += " from " + this.origins;
this.toString = function() { return s; };
return s;
}
}
function ABEPolicyChannel(origin, destination, method) {
this.originURI = origin;
this.URI = destination;
if (method) this.requestMethod = method;
}
ABEPolicyChannel.prototype = {
requestMethod: "GET",
cancelled: false,
loadFlags: 0,
cancel: function() {
this.cancelled = true;
}
}
function ABERequest(channel) {
this._init(channel);
}
ABERequest.serial = 0;
ABERequest.getOrigin = function(channel) {
return IOUtil.extractFromChannel(channel, "ABE.origin", true);
},
ABERequest.getLoadingChannel = function(window) {
return window && ("__loadingChannel__" in window) && window.__loadingChannel__;
},
ABERequest.storeOrigin = function(channel, originURI) {
IOUtil.attachToChannel(channel, "ABE.origin", originURI);
},
ABERequest.clear = function(channel, window) {
IOUtil.extractFromChannel(channel, "ABE.origin");
}
ABERequest.count = 0;
ABERequest.prototype = Lang.memoize({
external: false,
failed: false,
checkFlags: 0,
deferredDNS: true,
replaced: false,
_init: function(channel) {
this.serial = ABERequest.serial++;
this.channel = channel;
this.method = channel.requestMethod;
this.destinationURI = IOUtil.unwrapURL(channel.URI);
this.destination = this.destinationURI.spec;
this.early = channel instanceof ABEPolicyChannel;
this.isDoc = !!(channel.loadFlags & channel.LOAD_DOCUMENT_URI);
if (this.isDoc) {
var w = this.window;
if (w) w.__loadingChannel__ = channel;
}
var ou = ABERequest.getOrigin(channel);
if (ou) {
this.xOriginURI = this.originURI = ou;
this.xOrigin = this.origin = ou.spec;
this.replaced = true;
} else {
this.xOriginURI = this.early
? channel.originURI
: XOriginCache.pick(this.destinationURI, true) || // picks and remove cached entry
((channel.originalURI.spec != this.destination)
? channel.originalURI
: IOUtil.extractInternalReferrer(channel)
) || null;
this.xOrigin = this.xOriginURI && this.xOriginURI.spec || '';
var ou = this.xOrigin && this.xOriginURI;
if (!ou) {
if (channel instanceof CI.nsIHttpChannelInternal) {
ou = channel.documentURI;
if (!ou || ou.spec == this.destination) ou = null;
}
}
if (this.isDoc && (!ou || /^(?:javascript|data)$/i.test(ou.scheme))) {
ou = this.traceBack;
if (ou) ou = IOS.newURI(ou, null, null);
}
this.originURI = ou && IOUtil.unwrapURL(ou) || ABE.BROWSER_URI;
this.origin = this.originURI && this.originURI.spec || '';
ABERequest.storeOrigin(channel, this.originURI);
}
},
replace: function(newMethod, newURI) {
var replacement = new ChannelReplacement(this.channel, newURI, newMethod)
.replace(newMethod || newURI);
return replacement;
},
isBrowserURI: function(uri) {
return /^(?:chrome|resource)$/i.test(uri.scheme);
},
isLocal: function(uri, all) {
return DNS.isLocalURI(uri, all);
},
_checkLocalOrigin: function(uri) {
try {
return !this.failed && uri && (this.isBrowserURI(uri) || this.isLocal(uri, true)); // we cache external origins to mitigate DNS rebinding
} catch(e) {
ABE.log("Local origin DNS check failed for " + uri.spec + ": " + e);
try {
if (this.destinationURI.host == uri.host) {
this.channel.cancel(NS_ERROR_UNKNOWN_HOST);
this.failed = true;
}
} catch(e) {
}
return false;
}
},
_checkSelf: function(originURI) {
return originURI && (this.isBrowserURI(originURI) || originURI.prePath == this.destinationURI.prePath);
},
matchAllOrigins: function(matcher) {
var canDoDNS = this.canDoDNS;
return (canDoDNS && matcher.netMatching)
? matcher.testURI(this.originURI, canDoDNS, true) &&
this.redirectChain.every(function(uri) { return matcher.testURI(uri, canDoDNS, true); })
: matcher.test(this.origin) && this.redirectChain.every(matcher.testURI, matcher)
;
},
matchSomeOrigins: function(matcher) {
var canDoDNS = this.canDoDNS;
return (canDoDNS && matcher.netMatching)
? matcher.testURI(this.originURI, canDoDNS, false) ||
this.redirectChain.some(function(uri) { return matcher.testURI(uri, canDoDNS, false); })
: matcher.test(this.origin) || this.redirectChain.some(matcher.testURI, matcher)
;
},
toString: function() {
var s = "{" + this.method + " " + this.destination + " <<< " +
this.redirectChain.reverse().map(function(uri) { return uri.spec; }).concat(this.origin)
.join(", ") + "}";
this.toString = function() { return s; }
return s;
}
},
// lazy properties
{
traceBack: function() {
this.breadCrumbs = [this.destination];
return !this.early && OriginTracer.traceBack(this, this.breadCrumbs);
},
traceBackURI: function() {
var tbu = this.traceBack;
return tbu && IOS.newURI(tbu, null, null);
},
canDoDNS: function() {
return (this.channel instanceof CI.nsIChannel) && // we want to prevent sync DNS resolution for resources we didn't already looked up
IOUtil.canDoDNS(this.channel);
},
localOrigin: function() {
return this.canDoDNS && this._checkLocalOrigin(this.originURI) &&
this.redirectChain.every(this._checkLocalOrigin, this);
},
localDestination: function() {
try {
return !this.failed && this.canDoDNS && this.isLocal(this.destinationURI, false);
} catch(e) {
ABE.log("Local destination DNS check failed for " + this.destination +" from "+ this.origin + ": " + e);
this.channel.cancel(NS_ERROR_UNKNOWN_HOST);
this.failed = true;
return false;
}
},
isSelf: function() {
return this._checkSelf(this.originURI) && this.redirectChain.every(this._checkSelf, this);
},
isSubdoc: function() {
var channel = this.channel;
if (this.isDoc) {
var w = this.window;
return w != w.top;
}
return false;
},
redirectChain: function() {
return ABE.getRedirectChain(this.channel);
},
sameQueue: function() {
return this.isDoc || !!(this.channel.loadFlags & this.channel.LOAD_BACKGROUND) || !this.window
;
},
window: function() {
return IOUtil.findWindow(this.channel);
}
}
); // end memoize
var ABEStorage = {
_lastCheckTS: 0,
_delay: 360000, // wait 1 hour to check and fetch
_defaults: {
SYSTEM: "# Prevent Internet sites from requesting LAN resources.\r\nSite LOCAL\r\nAccept from LOCAL\r\nDeny",
USER: "# User-defined rules. Feel free to experiment here.\r\n\r\n"
},
_initDefaults: function(dir) {
try {
var f, content;
for (var d in this._defaults) {
f = dir.clone();
f.append(d + ".abe");
if (!f.exists()) {
f.create(f.NORMAL_FILE_TYPE, 0600);
IO.safeWriteFile(f, this._defaults[d]);
}
}
} catch(e) {
ABE.log(e);
}
},
_initDir: function(dir) {
if (!dir.exists()) try {
dir.create(dir.DIRECTORY_TYPE, 0755);
this._initDefaults(dir);
} catch(e) {
ABE.log(e);
}
},
get dir() {
var dir = CC["@mozilla.org/file/directory_service;1"].getService(
CI.nsIProperties).get("ProfD", CI.nsIFile);
dir.append("ABE");
dir.append("rules");
this._initDir(dir);
delete this.dir;
return this.dir = dir;
},
clear: function() {
this.dir.remove(true);
this._initDir(this.dir);
},
reset: function() {
this._lastCheckTS = 0;
},
getRulesetFile: function(name) {
var f = this.dir.clone();
f.append(name + ".abe");
return f;
},
loadRules: function() {
return !(this._lastCheckTS &&
Date.now() - this._lastCheckTS < this._delay) &&
this.loadRulesNow();
},
loadRulesNow: function() {
ABE.log("Checking for updated rules...");
var t = Date.now();
try {
var dir = this.dir;
try {
var entries = dir.directoryEntries;
} catch(e) {
this._initDir(dir);
entries = dir.directoryEntries;
}
var ff = [];
var mustUpdate = dir.lastModifiedTime > this._lastCheckTS;
var f;
while(entries.hasMoreElements()) {
f = entries.getNext();
if (f instanceof CI.nsIFile && /^[^\.\s]*\.abe$/i.test(f.leafName)) {
ff.push(f);
if (!mustUpdate && f.lastModifiedTime > this._lastCheckTS) mustUpdate = true;
}
}
if (!mustUpdate) return false;
ABE.log("Rules changed, reloading!")
ff.sort(function(a, b) { return a.leafName > b.leafName; });
var disabledNames = ABE.disabledRulesetNames;
ABE.clear();
ff.forEach(this.loadRuleFile, this);
} catch(e) {
ABE.log(e);
return false;
} finally {
this._lastCheckTS = Date.now();
ABE.log("Updates checked in " + (this._lastCheckTS - t) + "ms");
}
ABE.disabledRulesetNames = disabledNames;
return true;
},
loadRuleFile: function(f) {
try {
ABE.parse(f.leafName.replace(/\.abe$/i, ''), IO.readFile(f), f.lastModifiedTime);
} catch(e) {
ABE.log(e);
}
}
}
var XOriginCache = {
LIFE: 180000, // 3 mins, more than enough for most DNS timeout confs
PURGE_INTERVAL: 60000,
MAX_ENTRIES: 100,
_entries: [],
_lastPurge: Date.now(),
_lastDestination: null,
store: function(origin, destination) {
if (destination === this._lastDestination)
return; // we can afford it because we deal with nsIURI pointers and origins are invariants
this._lastDestination = destination;
var ts = Date.now();
this._entries.push({ o: origin, d: destination, ts: ts });
if (this._entries.length > this.MAX_ENTRIES)
this._entries.shift();
if (ts - this._lastPurge > this.PURGE_INTERVAL) this.purge(ts);
},
pick: function(destination, remove) {
var ee = this._entries;
for (var j = ee.length, e; j--> 0;) {
if ((e = ee[j]).d === destination) {
if (remove) ee.splice(j, 1);
return e.o;
}
}
return null;
},
purge: function(ts) {
ts = ts || Date.now();
var ee = this._entries;
var j = 0, len = ee.length;
for(; j < len && ee[j].ts + this.LIFE < ts; j++);
if (j > 0) ee.splice(0, j);
this._lastPurge = ts;
this._lastDestination = null;
}
}
var OriginTracer = {
detectBackFrame: function(prev, next, ds) {
if (prev.ID != next.ID) return prev.URI.spec;
if ((prev instanceof CI.nsISHContainer) &&
(next instanceof CI.nsISHContainer) &&
(ds instanceof CI.nsIDocShellTreeNode)
) {
var uri;
for (var j = Math.min(prev.childCount, next.childCount, ds.childCount); j-- > 0;) {
uri = this.detectBackFrame(prev.GetChildAt(j),
next.GetChildAt(j),
ds.GetChildAt(j));
if (uri) return uri.spec;
}
}
return null;
},
traceBackHistory: function(sh, window, breadCrumbs) {
var wantsBreadCrumbs = !breadCrumbs;
breadCrumbs = breadCrumbs || [window.document.documentURI];
var he;
var uri = null;
var site = '';
for (var j = sh.index; j > -1; j--) {
he = sh.getEntryAtIndex(j, false);
if (he.isSubFrame && j > 0) {
uri = this.detectBackFrame(sh.getEntryAtIndex(j - 1), h,
DOM.getDocShellForWindow(window)
);
} else {
// not a subframe navigation
if (window == window.top) {
uri = he.URI.spec; // top frame, return history entry
} else {
window = window.parent;
uri = window.document.documentURI;
}
}
if (!uri) break;
if (breadCrumbs[0] && breadCrumbs[0] == uri) continue;
breadCrumbs.unshift(uri);
if (!/^(?:javascript|data)$/i.test(uri.scheme)) {
site = uri;
break;
}
}
return wantsBreadCrumbs ? breadCrumbs : site;
},
traceBack: function(req, breadCrumbs) {
var res = '';
try {
ABE.log("Traceback origin for " + req.destination);
var window = req.window;
if (window instanceof CI.nsIInterfaceRequestor) {
var webNav = window.getInterface(CI.nsIWebNavigation);
var current = webNav.currentURI;
var isSameURI = current && current.equals(req.destinationURI);
if (isSameURI && (req.channel.loadFlags & req.channel.VALIDATE_ALWAYS))
return req.destination; // RELOAD
const sh = webNav.sessionHistory;
res = sh ? this.traceBackHistory(sh, window, breadCrumbs || null)
: (!isSameURI && current)
? req.destination
: '';
if (res == "about:blank") {
res = window.parent.location.href;
ns.dump(res);
}
}
} catch(e) {
ABE.log("Error tracing back origin for " + req.destination + ": " + e.message);
}
ABE.log("Traced back " + req.destination + " to " + res);
return res;
}
}